#include <stdio.h>
//#include <malloc.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <limits.h>
#include <assert.h>

#include "memfile.h"
#include "usuals.h"
#include "pgp.h"

#define ASSERT(e) assert(e)
#define ASSERT_VALID(o) (o != NULL)

/*//////////////////////////////////////////////////////////////////////////
/* MemFile implementation */

MemFile *mfopen(BYTE* lpBuffer, UINT nBufferSize, UINT nGrowBytes /* = 0*/)
{
	MemFile *f;

	ASSERT(nGrowBytes <= UINT_MAX);

	if((f = (MemFile *) malloc(sizeof(MemFile))) == NULL)
		return NULL;

	f->m_nGrowBytes = nGrowBytes;
	f->m_nPosition = 0;
	f->m_nBufferSize = nBufferSize;
	f->m_nFileSize = nGrowBytes == 0 ? nBufferSize : 0;
	f->m_lpBuffer = lpBuffer;
	f->m_bAutoDelete = FALSE;
	f->m_bWriteError = FALSE;
	return f;
}

MemFile *mfopen_w(UINT nGrowBytes /* = 1024 */)
{
	MemFile *f;

	ASSERT(nGrowBytes <= UINT_MAX);

	if((f = (MemFile *) malloc(sizeof(MemFile))) == NULL)
		return NULL;

	f->m_nGrowBytes = nGrowBytes;
	f->m_nPosition = 0;
	f->m_nBufferSize = 0;
	f->m_nFileSize = 0;
	f->m_lpBuffer = NULL;
	f->m_bAutoDelete = TRUE;
	f->m_bWriteError = FALSE;
	return f;
}

DWORD mftell(MemFile *f)
{
	ASSERT_VALID(f);
	return f->m_nPosition;
}

BOOL GrowMemFile(MemFile *f, DWORD dwNewLen)
{
	BYTE* lpNew;

	ASSERT_VALID(f);
	if (dwNewLen > f->m_nBufferSize)
	{
		/* grow the buffer */
		DWORD dwNewBufferSize = (DWORD)f->m_nBufferSize;

		/* watch out for buffers which cannot be grown! */
		ASSERT(f->m_bAutoDelete);
		ASSERT(f->m_nGrowBytes != 0);
		if (f->m_nGrowBytes == 0)
			return FALSE;

		/* determine new buffer size */
		while (dwNewBufferSize < dwNewLen)
			dwNewBufferSize += f->m_nGrowBytes;

		/* allocate new buffer */
		if (f->m_lpBuffer == NULL)
			lpNew = malloc(dwNewBufferSize);
		else
			lpNew = realloc(f->m_lpBuffer, dwNewBufferSize);

		if (lpNew == NULL)
			return FALSE;

		f->m_lpBuffer = lpNew;
		f->m_nBufferSize = dwNewBufferSize;
	}
	return TRUE;
}

UINT mfread(void* lpBuf, UINT nCount, MemFile *f)
{
	UINT nRead;

	ASSERT_VALID(f);

	if (nCount == 0)
		return 0;

	ASSERT(lpBuf != NULL);

	if (f->m_nPosition >= f->m_nFileSize)
		return 0;

	if (f->m_nPosition + nCount > f->m_nFileSize)
		nRead = (UINT)(f->m_nFileSize - f->m_nPosition);
	else
		nRead = nCount;

	memcpy(lpBuf, f->m_lpBuffer + f->m_nPosition, nRead);
	f->m_nPosition += nRead;

	return nRead;
}

UINT mfwrite(const void* lpBuf, UINT nCount, MemFile *f)
{
	ASSERT_VALID(f);

	if(f->m_bWriteError) return TRUE;
	if (nCount == 0) return 0;

	ASSERT(lpBuf != NULL);

	if (f->m_nPosition + nCount > f->m_nBufferSize)
		if(!GrowMemFile(f, f->m_nPosition + nCount)){
			f->m_bWriteError = TRUE;
			return EOF;
		}

	ASSERT(f->m_nPosition + nCount <= f->m_nBufferSize);

	memcpy(f->m_lpBuffer + f->m_nPosition, lpBuf, nCount);

	f->m_nPosition += nCount;

	if (f->m_nPosition > f->m_nFileSize)
		f->m_nFileSize = f->m_nPosition;
	return nCount;
}

LONG mfseek(MemFile *f, LONG lOff, UINT nFrom)
{
	LONG lNewPos = f->m_nPosition;

	ASSERT_VALID(f);
	ASSERT(nFrom == SEEK_SET || nFrom == SEEK_END || nFrom == SEEK_CUR);

	if (nFrom == SEEK_SET)
		lNewPos = lOff;
	else if (nFrom == SEEK_CUR)
		lNewPos += lOff;
	else if (nFrom == SEEK_END)
		lNewPos = f->m_nFileSize + lOff;
	else
		return -1;

	if (lNewPos < 0)
		return EOF;

	f->m_nPosition = lNewPos;

	return f->m_nPosition;
}

void mfrewind(MemFile *f)
{
	ASSERT_VALID(f);
	f->m_nPosition = 0;
}

int mfclose(MemFile *f)
{
	ASSERT_VALID(f);

	if (f->m_lpBuffer && f->m_bAutoDelete){
		memset(f->m_lpBuffer, 0, f->m_nBufferSize);
		free(f->m_lpBuffer);
	}
	f->m_nGrowBytes = 0;
	f->m_nPosition = 0;
	f->m_nBufferSize = 0;
	f->m_nFileSize = 0;
	f->m_lpBuffer = NULL;
	free(f);
	return 0;
}

int mfgetc(MemFile *f)
{
	ASSERT_VALID(f);

	if (f->m_nPosition >= f->m_nFileSize)
		return EOF;

	return f->m_lpBuffer[f->m_nPosition++];
}

int mfputc(int c, MemFile *f)
{
	ASSERT_VALID(f);

	if (f->m_nPosition + 1 > f->m_nBufferSize)
		if(!GrowMemFile(f, f->m_nPosition + 1)){
			f->m_bWriteError = TRUE;
			return EOF;
		}

	ASSERT(f->m_nPosition + 1 <= f->m_nBufferSize);

	f->m_lpBuffer[f->m_nPosition++] = c;

	if (f->m_nPosition > f->m_nFileSize)
		f->m_nFileSize = f->m_nPosition;
	return c;
}

int mfungetc(int c, MemFile *f)
{
	ASSERT_VALID(f);

	if(f->m_nPosition == 0)
		return EOF;
	f->m_lpBuffer[--f->m_nPosition] = c;
	return c;
}

BOOL mfwrite_error(MemFile *f)
{
	ASSERT_VALID(f);
	return f->m_bWriteError;
}

DWORD mfsize(MemFile *f)
{
	ASSERT_VALID(f);
	return f->m_nFileSize - f->m_nPosition;
}

int mfputs(const char *s, MemFile *f)
{
	ASSERT_VALID(f);
	return mfwrite(s, strlen(s), f);
}
